#include <iostream>
#include "VkBundle.h"
#include "VkHelper.h"
#include <GLFW/glfw3.h>
#include <limits>

/***** 全局变量 *****/
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600

GLFWwindow *window;
VkBundle vk;
bool canRender;

std::string shaders("shaders/");
const char *defaultShader = "demo001";
/***** 全局变量 *****/

/***** 内部函数 *****/
void createWindow();
static std::vector<char> readFile(const std::string& filePath){
    //从文件尾部开始读取
    std::ifstream file(filePath, std::ios::ate | std::ios::binary);
    if(!file.is_open()){
        throw std::runtime_error("can not open the file: " + filePath);
    }

    size_t fileSize = (size_t) file.tellg();
    std::vector<char> buffer(fileSize);
    file.seekg(0);//从头开始读
    file.read(buffer.data(), fileSize);
    file.close();
    return buffer;
}
/***** 内部函数 *****/

void init(){
    VkHelper::createInstance(true, &vk.instance, &vk.debugReport);
    createWindow();
    VkHelper::pickPhyDevAndCreateDev(vk.instance, vk.surface, &vk.deviceInfo, &vk.queueInfo);
    VkHelper::createCommandPool(vk.deviceInfo.device, vk.queueInfo.workQueueIndex, &vk.cmdPool);
    VkHelper::createSwapchain(vk.deviceInfo.physicalDev, vk.deviceInfo.device, vk.surface, vk.queueInfo.workQueueIndex,
                              vk.queueInfo.presentQueueIndex, &vk.swapchainParam,&vk.swapchain);
    CALL_VK(vkGetSwapchainImagesKHR(vk.deviceInfo.device, vk.swapchain, &vk.swapchainImage.imageCount, nullptr));
    vk.swapchainImage.images = static_cast<VkImage *>(malloc(sizeof(VkImage) * vk.swapchainImage.imageCount));
    CALL_VK(vkGetSwapchainImagesKHR(vk.deviceInfo.device, vk.swapchain, &vk.swapchainImage.imageCount, vk.swapchainImage.images));
    vk.swapchainImage.views = static_cast<VkImageView *>(malloc(sizeof(VkImageView) * vk.swapchainImage.imageCount));
    for(uint32_t i = 0; i < vk.swapchainImage.imageCount; i++){
        VkImageViewCreateInfo createInfo = {
                .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
                .pNext = nullptr,
                .flags = 0,
                .image = vk.swapchainImage.images[i],
                .viewType = VK_IMAGE_VIEW_TYPE_2D,
                .format = vk.swapchainParam.format.format,
                .components = {VK_COMPONENT_SWIZZLE_IDENTITY},
                .subresourceRange = {
                        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                        .baseMipLevel = 0,
                        .levelCount = 1,
                        .baseArrayLayer = 0,
                        .layerCount = 1
                }
        };
        CALL_VK(vkCreateImageView(vk.deviceInfo.device, &createInfo, VK_ALLOC, &vk.swapchainImage.views[i]));
    }

    VkHelper::createRenderPass(vk.deviceInfo.device, vk.swapchainParam, &vk.renderPass);
    vk.framebufferCount = vk.swapchainImage.imageCount;
    vk.framebuffers = static_cast<VkFramebuffer *>(malloc(sizeof(VkFramebuffer) * vk.framebufferCount));
    VkHelper::createFramebuffer(vk.deviceInfo.device, vk.renderPass, vk.swapchainParam.extent.width, vk.swapchainParam.extent.height,
                                vk.framebufferCount, vk.swapchainImage.views, vk.framebuffers);
    VkHelper::createPipelineLayout(vk.deviceInfo.device, &vk.pipelineLayout);
    auto vertexShaderCode = readFile(shaders + defaultShader + ".vert.spv");// ---> shaders/demo001.vert.spv
    auto fragShaderCode = readFile(shaders + defaultShader + ".frag.spv");// ---> shaders/demo001.frag.spv
    vk.vertexShaderModule = VkHelper::createShaderModule(vk.deviceInfo.device, vertexShaderCode);
    vk.fragShaderModule = VkHelper::createShaderModule(vk.deviceInfo.device, fragShaderCode);
    VkHelper::createPipeline(vk.deviceInfo.device, vk.pipelineLayout, vk.renderPass,
                             vk.vertexShaderModule, vk.fragShaderModule, vk.swapchainParam, &vk.graphicPipeline);
    vk.cmdBufferCount = vk.swapchainImage.imageCount;
    vk.cmdBuffers = static_cast<VkCommandBuffer *>(malloc(sizeof(VkCommandBuffer) * vk.cmdBufferCount));
    VkHelper::allocateCommandBuffers(vk.deviceInfo.device, vk.cmdPool, vk.cmdBufferCount, vk.cmdBuffers);

    VkSemaphoreCreateInfo semaphoreCreateInfo = {
            .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0
    };
    CALL_VK(vkCreateSemaphore(vk.deviceInfo.device, &semaphoreCreateInfo, VK_ALLOC, &vk.imageSemaphore));
    CALL_VK(vkCreateSemaphore(vk.deviceInfo.device, &semaphoreCreateInfo, VK_ALLOC, &vk.presentSemaphore));
    canRender = true;
}

void render(uint32_t currentFrame){
    if(!canRender) return;
    uint32_t imageIndex = 0;
    VkResult rt = vkAcquireNextImageKHR(vk.deviceInfo.device, vk.swapchain, std::numeric_limits<uint64_t>::max(), vk.imageSemaphore, VK_NULL_HANDLE, &imageIndex);

    // out of date: 窗口变化等情况会出现这个返回。本案例先不处理。
    if(rt == VK_ERROR_OUT_OF_DATE_KHR){
        Error("swapchain was out of date.");
        return;
    }

    //开启命令缓冲
    VkCommandBufferBeginInfo cmdBufferBeginInfo = {
            .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
            .pNext = nullptr,
            //VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT：指令缓冲在执行一次后，就被用来记录新的指令。
            //VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT：这是一个只在一个渲染流程内使用的辅助指令缓冲。
            //VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT：在指令缓冲等待执行时，仍然可以提交这一指令缓冲。
            .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
            .pInheritanceInfo = nullptr
    };
    CALL_VK(vkBeginCommandBuffer(vk.cmdBuffers[imageIndex], &cmdBufferBeginInfo));

    //开启渲染流程
    VkClearValue defaultClearValues = { 0.1f,  0.1f,  0.2f,  1.0f};
    VkRenderPassBeginInfo renderPassBeginInfo = {
            .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
            .pNext = nullptr,
            .renderPass = vk.renderPass,
            .framebuffer = vk.framebuffers[imageIndex],
            .renderArea = {
                    .offset = {0, 0},
                    .extent = vk.swapchainParam.extent
            },
            .clearValueCount = 1,
            .pClearValues = &defaultClearValues,
    };

    //VK_SUBPASS_CONTENTS_INLINE：所有要执行的指令都在主要指令缓冲中，没有辅助指令缓冲需要执行。
    //VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS：有来自辅助指令缓冲的指令需要执行。
    vkCmdBeginRenderPass(vk.cmdBuffers[imageIndex], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

    //绑定渲染管线
    vkCmdBindPipeline(vk.cmdBuffers[imageIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.graphicPipeline);

    /*
     * commandBuffer 命令缓冲
     * vertexCount 顶点数量
     * instanceCount 实例数量，我们目前只绘制一个实例。当绘制大批量对象时会用到。
     * firstVertex 起始顶点，用于偏移
     * firstInstance 起始实例，用于偏移
     */
    vkCmdDraw(vk.cmdBuffers[imageIndex], 3, 1, 0, 0);

    vkCmdEndRenderPass(vk.cmdBuffers[imageIndex]);
    CALL_VK(vkEndCommandBuffer(vk.cmdBuffers[imageIndex]));

    //将命令缓冲提交到队列
    VkPipelineStageFlags stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
    VkSubmitInfo submitInfo = {
            .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
            .pNext = nullptr,
            .waitSemaphoreCount = 1,
            .pWaitSemaphores = &vk.imageSemaphore,
            .pWaitDstStageMask = stages,
            .commandBufferCount = 1,
            .pCommandBuffers = &vk.cmdBuffers[imageIndex],
            .signalSemaphoreCount = 1,
            .pSignalSemaphores = &vk.presentSemaphore,
    };
    CALL_VK(vkQueueSubmit(vk.queueInfo.queue, 1, &submitInfo, VK_NULL_HANDLE));

    VkPresentInfoKHR presentInfo = {
            .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
            .pNext = nullptr,
            .waitSemaphoreCount = 1,
            .pWaitSemaphores = &vk.presentSemaphore,
            .swapchainCount = 1,
            .pSwapchains = &vk.swapchain,
            .pImageIndices = &imageIndex,
            .pResults = nullptr
    };
    CALL_VK(vkQueuePresentKHR(vk.queueInfo.queue, &presentInfo));
    CALL_VK(vkQueueWaitIdle(vk.queueInfo.queue));
}

void renderLoop(){
    uint32_t currentFrame = 0;
    while(!glfwWindowShouldClose(window)){

        glfwPollEvents();

        render(currentFrame);

        glfwSwapBuffers(window);
        currentFrame ++;
    }
}

void destroy(){
    Print("%s", __FUNCTION__);
    vkDeviceWaitIdle(vk.deviceInfo.device);

    vkDestroySemaphore(vk.deviceInfo.device, vk.presentSemaphore, VK_ALLOC);
    vkDestroySemaphore(vk.deviceInfo.device, vk.imageSemaphore, VK_ALLOC);
    vkFreeCommandBuffers(vk.deviceInfo.device, vk.cmdPool, vk.cmdBufferCount, vk.cmdBuffers);
    free(vk.cmdBuffers);
    vkDestroyPipeline(vk.deviceInfo.device, vk.graphicPipeline, VK_ALLOC);
    vkDestroyShaderModule(vk.deviceInfo.device, vk.vertexShaderModule, VK_ALLOC);
    vkDestroyShaderModule(vk.deviceInfo.device, vk.fragShaderModule, VK_ALLOC);
    vkDestroyPipelineLayout(vk.deviceInfo.device, vk.pipelineLayout, VK_ALLOC);
    for (size_t i = 0; i < vk.framebufferCount; i++) {
        vkDestroyFramebuffer(vk.deviceInfo.device, vk.framebuffers[i], VK_ALLOC);
    }
    free(vk.framebuffers);
    for(uint32_t i = 0; i < vk.swapchainImage.imageCount; i++){
        vkDestroyImageView(vk.deviceInfo.device, vk.swapchainImage.views[i], VK_ALLOC);
    }
    free(vk.swapchainImage.views);
    free(vk.swapchainImage.images);
    vkDestroyRenderPass(vk.deviceInfo.device, vk.renderPass, VK_ALLOC);
    vkDestroySwapchainKHR(vk.deviceInfo.device, vk.swapchain, VK_ALLOC);
    vkDestroyCommandPool(vk.deviceInfo.device, vk.cmdPool, VK_ALLOC);
    vkDestroyDevice(vk.deviceInfo.device, VK_ALLOC);
    vkDestroySurfaceKHR(vk.instance, vk.surface, VK_ALLOC);
    vkDestroyInstance(vk.instance, VK_ALLOC);
    glfwDestroyWindow(window);
    glfwTerminate();
}

int main() {
    std::cout << "Hello, Learn Vulkan!" << std::endl;

    init();
    renderLoop();
    destroy();

    return 0;
}

void createWindow(){
    if(!glfwInit()){
        throw std::runtime_error("Can not init glfw window.");
    }
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // 暂时不允许改变大小，改变大小后需要重置交换链，后续demo实现
    window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, APP_NAME, nullptr, nullptr);
    if(!window){
        throw std::runtime_error("Can not create glfw window");
    }

    Print("GLFW Vulkan Support: %d", glfwVulkanSupported());
    glfwMakeContextCurrent(window);

    if(glfwCreateWindowSurface(vk.instance, window, VK_ALLOC, &vk.surface) != VK_SUCCESS){
        throw std::runtime_error("Can not creat surface.");
    }
    Print("surface: %p", vk.surface);
}